/*
 *
 * C++ MaJiang for 血流红中
 *
 * TuyooGame, 2021
 * zhouxin@tuyoogame.com
 *
 */
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>

#include "log.h"
#include "mj.h"

void  *thread_worker(void *arg);

#define THREAD_COUNT 20

//日志锁...
pthread_mutex_t _log_lock;
void log_locker(bool lock, void *udata) {
    if(lock)
        pthread_mutex_lock((pthread_mutex_t *)udata);
    else
        pthread_mutex_unlock((pthread_mutex_t *)udata);
}

MJCache *g_cache;

//./mjsvr <udp_listen_port>
//./mjsvr 8888
int 
main(int argc, char **argv) {
    pthread_mutex_init(&_log_lock, NULL);
    log_set_lock(log_locker, (void *)&_log_lock);
    log_set_level(LOG_DEBUG);

    if (argc != 2) {
        log_error("(mjsvr): Invoke as  './server socknum'\n");
        log_error("(mjsvr): for example, './server 4434'\n");
        exit(-1);
    }

    log_info("MJ hasTing Server[thread_pool_size=%d].\n", THREAD_COUNT);
    log_info("zhouxin@tuyoogame.com.............2021.\n");
    log_info("Loading cache...");
    g_cache = new MJCache();
    g_cache->loadCache("./", true);

    int listenfd;
    struct sockaddr_in servaddr;

    // Create a UDP Socket
    memset(&servaddr, 0, sizeof(servaddr));
    listenfd = socket(AF_INET, SOCK_DGRAM, 0);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(atoi(argv[1]));
    servaddr.sin_family = AF_INET;

    // Reuseport可以多个进程监听一个端口，操作系统均衡的发放消息
    // Linux kernel > 3.9
    int ret = 0, reuse = 1;
    ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&reuse , sizeof(int));
    if (ret < 0) {
        log_fatal("setsockopt reuseaddr error!\n");
        return -1;
    }
    ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&reuse , sizeof(int));
    if (ret < 0) {
        log_fatal("setsockopt reuseport error!\n");
        return -1;
    }

    // bind server address to socket descriptor
    if(::bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))<0) {
        log_fatal("bind error...\n");
        return 1;
    }
    log_info("Listen on UDP port:%s...\n", argv[1]);

    pthread_t pool[THREAD_COUNT];
    for(int i=0; i<THREAD_COUNT; i++) 
        pthread_create(&(pool[i]), NULL, thread_worker, (void *)&listenfd);
    for(int i=0; i<THREAD_COUNT; i++) 
        pthread_join(pool[i], NULL);
}

void *
thread_worker(void *args) {
    pthread_t tid = pthread_self();
    int listenfd = *((int *)args);
    struct sockaddr_in cliaddr;
    char sbuf[OUTPUT_SIZE];
    // 线程有自己的xc实例，算番判胡不用加锁
    XueLiuHZCard *xc = new XueLiuHZCard();

    while(1) {
        socklen_t len = sizeof(cliaddr);
        int n = recvfrom(listenfd, xc->input_buffer, INPUT_SIZE, 
                         0, (struct sockaddr*)&cliaddr, &len);
        xc->input_size = n;
        xc->input_buffer[n] = 0;
        char *p = xc->input_buffer;

        //将内存Cache写入3个文本文件，过程加锁，需要时间可能很长...
        //线上要谨慎使用DUMP命令
        if(p[0]=='D' and p[1]=='U' and p[2]=='M' && p[3]=='P') {
            g_cache->dumpCache("./", true);
            sendto(listenfd, "DUMP_OK...", 11, 
                   0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
            return 0;
        }

        if(xc->loadInput()) {
            int ret = g_cache->getFromCache(xc);
            log_debug("(%" PRIu64 ")cache ret=%d\n", (uint64_t)tid, ret);
            sendto(listenfd, xc->output_buffer, xc->output_size, 
                   0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
        } else {
            snprintf(sbuf, OUTPUT_SIZE, "INPUT_ERROR:%s", xc->input_buffer);
            sendto(listenfd, sbuf, strlen(sbuf), 
                   0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
        }
    }
    return 0;
}
